/*
University of Pernambuco - UPE
ECOMP - Computer Enginerring Department
 */

package hidra.metaheuristics.mopsodfr;

import hidra.core.population.HIDRAPopulationAlgorithm;
import hidra.gui.MOPSODFRJFrame;
import hidra.gui.PlotManager;
import hidra.jmetal.core.Operator;
import hidra.jmetal.core.Problem;
import hidra.jmetal.core.Solution;
import hidra.jmetal.core.SolutionSet;
import hidra.jmetal.core.Variable;
import hidra.metaheuristics.mopsocdr.util.ParetoFrontUtil;
import hidra.metaheuristics.mopsocdr.util.RoletteWheelSelection;
import hidra.metaheuristics.mopsocdr.util.TurbulenceMutation;
import hidra.qualityIndicator.Hypervolume;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;

import org.jfree.chart.ChartPanel;

import jmetal.util.Distance;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;
import jmetal.util.archive.CrowdingArchive;
import jmetal.util.archive.DiversityFactorArchive;
import jmetal.util.comparators.CrowdingDistanceComparator;
import jmetal.util.comparators.DiversityFactorComparator;
import jmetal.util.comparators.DominanceComparator;
import jmetal.util.wrapper.XReal;

/**
 * @author Dennis Cunha
 * @email  drcs@ecomp.poli.br
 */

public class MOPSODFR extends HIDRAPopulationAlgorithm{

	private enum State {
		CONVERGE,
		PROFUNDIDADE,
		AMPLITUDE,
		ESCAPE;
	}

	private PlotManager plot_manager;

	private int swarmSize_;

	private int archiveSize_;

	private SolutionSet archive_;
	
	private int maxEvaluations, evaluations;
	
	//private int maxIterations_;

	//private int iteration_;

	private SolutionSet particles_;

	private Solution[] best_;

	private DiversityFactorArchive leaders_;

	private double[][] speed_;

	private Comparator dominance_;

	//private Comparator crowdingDistanceComparator_;

	private Comparator diversityFactorComparator_;

	private Distance distance_;

	private Operator turbulence_;	

	private RoletteWheelSelection rollete;

	private double C1_;
	private double C2_;
	private double wMax_;
	private double wMin_;

	private static final double MUTATION_PERCENTUAL_MOPSO = 0.5;

	public MOPSODFR(Problem problem) {                
		super (problem);		

		this.plot_manager = new PlotManager();
		this.plot_manager.setupPlotExternalArchive();
		this.plot_manager.setupPlotSwarm();

	}

	public void initParams() {

		swarmSize_ = ((Integer) getInputParameter("swarmSize")).intValue();
		archiveSize_ = ((Integer) getInputParameter("archiveSize")).intValue();
		maxIterations_ = ((Integer) getInputParameter("maxIterations")).intValue();
		maxEvaluations = ((Integer)getInputParameter("maxEvaluations")).intValue();
		
		this.rollete = new RoletteWheelSelection();

		iteration_ = 0 ;

		particles_ = new SolutionSet(swarmSize_);
		best_ = new Solution[swarmSize_];
		//TODO: leaders_ = new CrowdingArchive(archiveSize_, problem_.getNumberOfObjectives());
		leaders_ = new DiversityFactorArchive(archiveSize_, problem_.getNumberOfObjectives());

		// Create comparators for dominance and crowding distance
		dominance_ = new DominanceComparator();
		//crowdingDistanceComparator_ = new CrowdingDistanceComparator();
		diversityFactorComparator_ = new DiversityFactorComparator();
		distance_ = new Distance();
		//distance = new UtilDistance();

		// Create the speed vector
		speed_ = new double[swarmSize_][problem_.getNumberOfVariables()];


		HashMap<String, Object> parameters = new HashMap<String, Object>();


		parameters.put("maxIterations",maxIterations_);
		parameters.put("percMutation", MUTATION_PERCENTUAL_MOPSO);

		this.turbulence_ = new TurbulenceMutation(parameters);
		
		archive_     = new SolutionSet(archiveSize_);
		
		this.evaluations = 0;
		this.C1_ = 1.49445;
		this.C2_ = 1.49445;
		this.wMax_ = 0.4;
		this.wMin_ = 0.0;
	}




	@Override
	protected SolutionSet getParetoFront() {
		return this.leaders_;
	}



	@Override
	protected SolutionSet initializationAlgorithm()
			throws ClassNotFoundException, JMException {


		initParams();

		//->Step 1 (and 3) Create the initial population and evaluate
		for (int i = 0; i < swarmSize_; i++){
			Solution particle = new Solution(problem_);
			problem_.evaluate(particle);
			problem_.evaluateConstraints(particle);
			evaluations++;
			particles_.add(particle);
		}


		//-> Step2. Initialize the speed_ of each particle to 0
		for (int i = 0; i < swarmSize_; i++) {
			for (int j = 0; j < problem_.getNumberOfVariables(); j++) {
				speed_[i][j] = 0.0;
			}
		}

		// Step4 and 5   
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}

		//-> Step 6. Initialize the memory of each particle
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			best_[i] = particle;
		}

		//Crowding the leaders_
		//ParetoFrontUtil.crowdingDistanceAssignmentToMOPSOCDR(leaders_, problem_.getNumberOfObjectives());
		ParetoFrontUtil.diversityFactorAssignmentToMOPSODFR(leaders_);

		return this.leaders_;

	}



	@Override
	protected SolutionSet runIteration() throws JMException {

		computeSpeed();

		//System.out.println(iteration_);

		//Compute the new positions for the particles_            
		computeNewPositions();

		//Mutate the particles_          
		mopsoMutation();

		//Evaluate the new particles_ in new positions
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = particles_.get(i);
			problem_.evaluate(particle);
			problem_.evaluateConstraints(particle);
		}

		//Actualize the archive          
		for (int i = 0; i < particles_.size(); i++) {
			Solution particle = new Solution(particles_.get(i));
			leaders_.add(particle);
		}
		
		Hypervolume hv = new Hypervolume();
		int previousValue = -1;
		int numberOfEvaluations = maxEvaluations/20;
		
//		System.out.println(leaders_.size());
//		System.exit(0);
		
		
//		if(evaluations/numberOfEvaluations != previousValue){
//		  	  previousValue = evaluations/numberOfEvaluations;
//		  	  
//		  	  double externalArchive[][] = new double[leaders_.size()][problem_.getNumberOfObjectives()];
//		  	  
//		  	  for(int i = 0; i < leaders_.size(); i++){
//		  		  for(int j = 0; j < problem_.getNumberOfObjectives(); j++){
//		  			  externalArchive[i][j] = leaders_.get(i).getObjective(j);
//		  		  }
//		  	  }
//		  	  
//		  	  System.out.println(hv.calculateHypervolume(externalArchive, leaders_.size(), 2));
//		    }

		//Assign crowding distance to the leaders_
		//		ParetoFrontUtil.crowdingDistanceAssignmentToMOPSOCDR(leaders_,
		//				problem_.getNumberOfObjectives());
		ParetoFrontUtil.diversityFactorAssignmentToMOPSODFR(leaders_);

		iteration_++;

		//Update Particles of the Memory
		for (int i = 0; i < particles_.size(); i++) {

			int flag = dominance_.compare(particles_.get(i), best_[i]);
			if (flag == -1) { // the new particle is best than the older pBest        
				Solution particle = new Solution(particles_.get(i));
				best_[i] = particle;
			}else if(flag == 0){

				Solution solNearParticle = 
						ParetoFrontUtil.getNearSolution(this.leaders_, particles_.get(i));

				Solution solNearPBest = 
						ParetoFrontUtil.getNearSolution(this.leaders_, best_[i]);

				if(solNearParticle.getDiversityFactor() > solNearPBest.getDiversityFactor()){
					best_[i] = new Solution(particles_.get(i));
				}
			}
		}

		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		plot_manager.plotExternalArchive(this.leaders_, iteration_ + 1);
		plot_manager.plotSwarm(this.particles_, iteration_ + 1);

		return this.leaders_;

	}

	// Adaptive inertia 
	private double inertiaWeight() {    	
		double w = wMax_ - (((wMax_-wMin_)*(double)iteration_)/(double)maxIterations_);
		if ( w < 0.0 ) {
			w = 0.0;          
		} 
		return w;
	} // inertiaWeight

	private void computeSpeed() throws JMException {
		double r1, r2;
		double wmax, wmin, W;
		XReal bestGlobal;


		wmax = wMax_;
		wmin = wMin_;

		W = inertiaWeight();
		double summatory = 0.0;

		SolutionSet front = new SolutionSet(leaders_.size());
		for (int i = 0; i < leaders_.size(); i++){
			front.add(leaders_.get(i));
		}

		front.sort(diversityFactorComparator_);
		for (int i=0; i < front.size() ;i++) {
			summatory = summatory + front.get(i).getDiversityFactor();
		}

		for (int i = 0; i < swarmSize_; i++) {
			XReal particle = new XReal(particles_.get(i)) ;
			XReal bestParticle = new XReal(best_[i]) ;

			bestGlobal = new XReal(rollete.executeDF(front, summatory));

			for (int var = 0; var < particle.getNumberOfDecisionVariables(); var++) {

				// generate stochastic components for each dimension
				r1 = PseudoRandom.randDouble(0.0, 1.0);
				r2 = PseudoRandom.randDouble(0.0, 1.0);


				//Computing the velocity of this particle 
				speed_[i][var] =  (W *
						speed_[i][var] +
						C1_ * r1 * (bestParticle.getValue(var) -
								particle.getValue(var)) +
								C2_ * r2 * (bestGlobal.getValue(var) -
										particle.getValue(var)));

				double vmax = particle.getUpperBound(var) - particle.getLowerBound(var);

				speed_[i][var] = 
						Math.signum(speed_[i][var]) * 
						Math.min(Math.abs(speed_[i][var]),vmax);

			}
		}
	} // computeSpeed


	/**
	 * Apply a mutation operator to some particles in the swarm
	 * @throws JMException 
	 */
	private void mopsoMutation() throws JMException {

		turbulence_.setParameter("currentIteration", iteration_);

		for (int i = 0; i < particles_.size(); i++) {
			turbulence_.execute(particles_.get(i));
		}

	} // mopsoMutation

	/**
	 * Update the position of each particle
	 * @throws JMException 
	 */
	private void computeNewPositions() throws JMException{
		for (int i = 0; i < swarmSize_; i++){
			Variable[] particle = particles_.get(i).getDecisionVariables();
			//particle.move(speed_[i]);
			for (int var = 0; var < particle.length; var++){
				particle[var].setValue(particle[var].getValue()+ speed_[i][var]);
				if (particle[var].getValue() < problem_.getLowerLimit(var)){
					particle[var].setValue(problem_.getLowerLimit(var));                    
					speed_[i][var] = speed_[i][var] * -1.0;    
				}
				if (particle[var].getValue() > problem_.getUpperLimit(var)){
					particle[var].setValue(problem_.getUpperLimit(var));                    
					speed_[i][var] = speed_[i][var] * -1.0;
				}
			}
		}
	} // computeNewPositions

	private Solution findBestSolutionRandomObjetive() {
		Solution objectiveLeader = null;

		int numObjectives = problem_.getNumberOfObjectives();
		Random rand = new Random();
		int randomObjective = rand.nextInt(numObjectives) + 1;

		//TODO: Selecionar melhor part�cula no objetivo escolhido

		return objectiveLeader;
	}

	// updating c1 and c2 values according to evolutionary factor
	private void updateFactor () throws JMException {
		double evolFactor = calculateEFactorBestParticle();
		State state = selectState(evolFactor);

		if (state.equals(State.AMPLITUDE)) {
			incFactor(this.C1_);
			decFactor(this.C2_);
		} else if (state.equals(State.PROFUNDIDADE)) {
			incLevFactor(this.C1_);
			decLevFactor(this.C2_);
		} else if (state.equals(State.CONVERGE)) {
			incLevFactor(this.C1_);
			incLevFactor(this.C2_);
		} else {
			decFactor(this.C1_);
			incFactor(this.C2_);
		}

	}

	// incrementing cFactor (times 0.5)
	private void incLevFactor(double cFactor) {
		cFactor += 0.5 * PseudoRandom.randDouble(0.05, 0.01);
	} 

	// incrementing cFactor (times 0.5)
	private void incFactor(double cFactor) {
		cFactor += PseudoRandom.randDouble(0.05, 0.01);
	}

	// decrementing cFactor
	private void decLevFactor(double cFactor) {
		cFactor -= 0.5 * PseudoRandom.randDouble(0.05, 0.01);
	}

	// decrementing cFactor
	private void decFactor(double cFactor) {
		cFactor -= PseudoRandom.randDouble(0.05, 0.01);
	}

	// calculating evolutionary factor for the entire external archive
	private double[] calculateEFactorEA() throws JMException {
		int EASize = leaders_.size();
		double[] EFactors = new double[EASize]; 
		double min = 0;
		double max = 0;
		double best = 0;
		double[] distances;

		// calculating average distance within the external archive
		distances = averageDistanceEA();

		// finding min and max distances
		min = getMinDistance(distances);
		max = getMaxDistance(distances);

		// calculating each evolutionary factor for every
		// solution in the external archive
		for (int i = 0; i < EASize; i++){
			Variable[] sol = leaders_.get(i).getDecisionVariables();
			best = bestDistanceEA(sol, 0);

			EFactors[i] =  (best - min) / (max - min);
		}

		return EFactors;
	}

	// calculating evolutionary factor for a single best particle
	private double calculateEFactorBestParticle() throws JMException {

		double min = 0;
		double max = 0;
		double best = 0;
		double[] distances;

		distances = averageDistance();
		min = getMinDistance(distances);
		max = getMaxDistance(distances);

		// TODO: Substituir essa solu��o pelo gBest (lider) 
		Variable[] sol = particles_.get(0).getDecisionVariables();
		best = bestDistance(sol, 0);

		return (best - min) / (max - min);

	}

	// calculating the average distance from the best particle to the others
	private double bestDistanceEA(Variable[] solution, int index) throws JMException {
		double dist_ret = 0;
		double dist = 0;
		double aux = 0;
		for (int i = 0; i < leaders_.size() && i != index; i++) {
			Variable[] sol = leaders_.get(i).getDecisionVariables();

			aux = 0;
			for (int dim = 0; dim < sol.length; dim++) {
				aux += Math.pow(solution[dim].getValue() - sol[dim].getValue() , 2);
			}
			dist += Math.sqrt(aux);
		}

		dist_ret = dist / (leaders_.size() - 1);

		return dist_ret;
	}

	// calculating the average distance from the best particle to the others
	private double bestDistance(Variable[] best, int index) throws JMException {
		double dist_ret = 0;
		double dist = 0;
		double aux = 0;
		for (int i = 0; i < swarmSize_ && i != index; i++) {
			Variable[] sol = particles_.get(i).getDecisionVariables();

			aux = 0;
			for (int dim = 0; dim < sol.length; dim++) {
				aux += Math.pow(best[dim].getValue() - sol[dim].getValue() , 2);
			}
			dist += Math.sqrt(aux);
		}

		dist_ret = dist / (swarmSize_ - 1);

		return dist_ret;
	}

	// calculating the average pareto front distance
	private double[] averageDistanceEA () throws JMException {
		int size = leaders_.size();
		double ret[] = new double[size];
		double temp = 0;
		double distance = 0;
		for (int i = 0; i < size; i++) {
			distance = 0;
			for (int j = 0; j < size && i != j; j++) {
				Variable[] solSource = leaders_.get(i).getDecisionVariables();
				Variable[] solDestination = leaders_.get(j).getDecisionVariables();

				temp = 0;
				for (int k = 0; k < solSource.length; k++) {
					temp += Math.pow(solSource[k].getValue() - solDestination[k].getValue(), 2); 
				}
				distance += Math.sqrt(temp);
			}
			ret[i] =  distance / (size - 1);
		}

		return ret;
	}

	// calculating the average swarm distance
	private double[] averageDistance () throws JMException {
		double ret[] = new double[swarmSize_];
		double temp = 0;
		double distance = 0;
		for (int i = 0; i < swarmSize_; i++) {
			distance = 0;
			for (int j = 0; j < swarmSize_ && i != j; j++) {
				Variable[] solSource = particles_.get(i).getDecisionVariables();
				Variable[] solDestination = particles_.get(j).getDecisionVariables();

				temp = 0;
				for (int k = 0; k < solSource.length; k++) {
					temp += Math.pow(solSource[k].getValue() - solDestination[k].getValue(), 2); 
				}
				distance += Math.sqrt(temp);
			}
			ret[i] =  distance / (swarmSize_ - 1);
		}

		return ret;
	}

	// finding min value within distances
	private double getMinDistance (double[] distances) {

		double[] _distances = distances.clone();
		Arrays.sort(_distances);

		return _distances[0];

	}

	// finding max value within distances
	private double getMaxDistance (double[] distances) {

		double[] _distances = distances.clone();
		Arrays.sort(_distances);
		int size = _distances.length;

		return _distances[size - 1];
	}

	// selecting swarm state according to evolutionar factor
	private State selectState (double evolFactor) {

		double[] _states = new double[4]; 

		double convVal = _EConverge(evolFactor);
		double amplVal = _EAmplitude(evolFactor);
		double profVal = _EProfundidade(evolFactor);
		double escVal = _EEscape(evolFactor);

		_states[0] = convVal;
		_states[1] = amplVal;
		_states[2] = profVal;
		_states[3] = escVal;

		// finding max value in _states
		Arrays.sort(_states);
		double higherVal = _states[3];

		// checking the swarm state
		if (higherVal == convVal) {
			return State.CONVERGE;
		} else if (higherVal == amplVal) {
			return State.AMPLITUDE;
		} else if (higherVal == profVal) {
			return State.PROFUNDIDADE;
		} else {
			return State.ESCAPE;
		}

	}

	// convergence pertinence function
	private double _EConverge (double evolFactor) {
		double retValue = 0;
		if (evolFactor >= 0.0 && evolFactor <= 0.1) {
			retValue = 1.0;
		} else if (evolFactor > 0.1 && evolFactor <= 0.3) {
			retValue = -5 * evolFactor + 1.5;
		}
		return retValue;
	}

	// exploitation pertinence function
	private double _EProfundidade(double evolFactor) {
		double retValue = 0;
		if (evolFactor > 0.2 && evolFactor <= 0.3) {
			retValue = 10 * evolFactor - 2.0;
		} else if (evolFactor > 0.3 && evolFactor <= 0.4) {
			retValue = 1.0;
		} else if (evolFactor > 0.4 && evolFactor <= 0.6) {
			retValue = -5 * evolFactor + 3.0;
		}
		return retValue;
	}

	// exploration pertinence function
	private double _EAmplitude (double evolFactor) {
		double retValue = 0;
		if (evolFactor > 0.4 && evolFactor <= 0.6) {
			retValue = 5 * evolFactor - 2.0;
		} else if (evolFactor > 0.6 && evolFactor <= 0.7) {
			retValue = 1.0;
		} else if (evolFactor > 0.7 && evolFactor <= 0.8) {
			retValue = -10 * evolFactor + 8;
		}
		return retValue;
	}

	// escaping pertinence function
	private double _EEscape (double evolFactor) {
		double retValue = 0;
		if (evolFactor > 0.7 && evolFactor <= 0.9) {
			retValue = 5 * evolFactor - 3.5;
		} else if (evolFactor > 0.9 && evolFactor <= 1.0) {
			retValue = 1.0;
		}
		return retValue;
	}

	public ChartPanel getChartPanelExternalArchive() {
		return plot_manager.getChartPanelExternalArchive();
	}

	public void setChartPanelExternalArchive (ChartPanel chartPanelExternalArchive) {
		this.plot_manager.setChartPanelExternalArchive(chartPanelExternalArchive);
	}

	public ChartPanel getChartPanelSwarm() {
		return plot_manager.getChartPanelSwarm();
	}

	public void getChartPanelSwarm(ChartPanel chartPanelSwarm) {
		this.plot_manager.setChartPanelSwarm(chartPanelSwarm);
	}

}
